/*
dcf77.c V2.0 (C) www.captain.at (C) www.ulrichradig.de
Changelog from V1.0 to V2.0:
* fixed "59th second" problem - after the 58th bit was received,
  at 59.1s or 59.2s the data of the next minute was loaded into
  the date/time variables and on the first second of the next
  minute the second counter already was incremented to 1.
  This is now fixed.
* added current_time_is_valid_dcf_time flag - if this flag is 1,
  the actual DCF77 time was correctly received for the current
  minute. If the flag is 0, there might have been a parity error
  in the DCF signal, or there is no signal at all. In a production
  environment, one would use a second TIMER for incrementing real
  date/time variables and just sync date/time every 10 minutes or
  so. Don't forget to check the current_time_is_valid_dcf_time flag
  and before reading (synching) the date/time values, don't forget
  to disable the interrupts to avoid reading a bogus date/time. 
*/
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
#include <avr/iom16.h>

// crystal frequency in Hz
#define F_OSC 2457600
// oscillator frequencies that match well for the timer AND USART BAUD rate:
// frequency  div    timer count
//  2457600 / 1024 =  2400
//  7372800 / 1024 =  7200
// 11059200 / 1024 = 10800
// 14745600 / 1024 = 14400

#define UART_BAUD_RATE 9600
#define UART_BAUD_CALC(UART_BAUD_RATE,F_OSC) ((F_OSC)/((UART_BAUD_RATE)*16l)-1)

unsigned char sec   = 0;
unsigned char min   = 0;
unsigned char hour = 0;
unsigned char day   = 0;
unsigned char month = 0;
unsigned int  year = 0;
unsigned char bit_counter = 0;
// 64 bits: 59 bits are used
unsigned long long dcf77_buffer = 0;
unsigned int timercounts = 0;
unsigned int flag59 = 0;
unsigned int setbeginningofminute = 0;

unsigned int current_time_is_valid_dcf_time = 0;

#define DCF77_INT            SIG_INTERRUPT0
#define INT0_CONTROL         MCUCR
#define INIT_TIMER_COUNT   (unsigned int)(65535 - (F_OSC / 1024))
#define RESET_TIMER0         TCNT1 = INIT_TIMER_COUNT

#define INIT_TIMER_COUNT_59   (unsigned int)(65535 - (F_OSC / 1024) - 100)
#define RESET_TIMER0_59         TCNT1 = INIT_TIMER_COUNT_59

#define PERCENT9X (unsigned int)( (F_OSC / 1024) / 100 * 98) // 98% percent

//#define DEBUG
//#define DEBUGEDGES
#define TIMEMSGS

struct DCF77 {
   unsigned long long bits       :16; // bits 1 to 16: reserved or not needed here
   unsigned long long mez_mesz   :1; // 1 at transition from MEZ to MESZ or vice versa
   unsigned long long zone1      :1; // 0=MEZ, 1=MESZ
   unsigned long long zone2      :1; // 0=MESZ, 1=MEZ
   unsigned long long leapsecond :1; // If 1, a leap-second is inserted at end of hour
   unsigned long long start      :1; // start bit is always 1
   unsigned long long min        :7; // 7 bits for minutes
   unsigned long long parity_min :1; // parity bit for minutes
   unsigned long long hour       :6; // 6 bits for hour
   unsigned long long parity_hour :1; // parity bit for hour
   unsigned long long day        :6; // 6 bits for day
   unsigned long long weekday    :3; // 3 bits for weekday
   unsigned long long month      :5; // 5 bits for month
   unsigned long long year       :8; // 8 bits for year (5 = 2005)
   unsigned long long parity_date :1; // parity bit for date
};

struct {
   unsigned char parity_flag   :1;
   unsigned char parity_min      :1;
   unsigned char parity_hour   :1;
   unsigned char parity_date   :1;
} flags;

void delay_ms(unsigned short ms) {
   unsigned short outer1, outer2;
   outer1 = 200; 
   while (outer1) {
      outer2 = 1000;
      while (outer2) {
         while ( ms ) ms--;
         outer2--;
      }
      outer1--;
   }
}

int usart_putc(char c) { // output one character via RS232
   // wait until UDR ready
   while(!(UCSRA & (1 << UDRE)));
   UDR = c; // send character
   return (0);
}

void uart_puts (char *s) { // output string via RS232
   //  loop until *s != NULL
   while (*s) { usart_putc(*s); s++; }
}

void init(void) {
   // set baud rate
   UBRRH = (uint8_t)(UART_BAUD_CALC(UART_BAUD_RATE,F_OSC) >> 8);
   UBRRL = (uint8_t)UART_BAUD_CALC(UART_BAUD_RATE,F_OSC);
   // Enable receiver and transmitter; enable RX interrupt
   UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE);
   //asynchronous 8N1
   UCSRC = (1 << URSEL) | (3 << UCSZ0);
   // All printf's are sent via the serial port (RS232)
   // This is just for a neat output of the date/time data:
   // printf incredibly blows up the code
   fdevopen(usart_putc, NULL, 0);
}

// INTERRUPT can be interrupted
// SIGNAL can't be interrupted
SIGNAL (SIG_UART_RECV) { // USART RX interrupt
   unsigned char c;
   // read received character
   c = UDR;
   // and just send it back
   usart_putc(c);
}

void add_second (void) { // incomplete implementation
   sec++;
   if (sec == 60) {
      sec = 0;
      min++;
      if (min == 60) {
         min = 0;
         hour++;
         if (hour == 24) {
            hour = 0;
            day++;
         }
      }
   }
}

SIGNAL (SIG_OVERFLOW1) {   
   // UPDATE: 59th second is now set correctly 1 second after the 58th second
   if (flag59 == 1) {
      RESET_TIMER0_59; // slightly longer timer interval, so that we don't
                       //   overlap with the actual DCF77 signal at the beginning
                   //   of the minute
      add_second();
      flag59 = 0;
      // next DCF77 pulse is the beginning of the new minute
      setbeginningofminute = 1;

   } else {
      RESET_TIMER0;
      // we only end up here if there is no DCF77 signal and the TIMER0
      //   is not reset timely => clock runs on internal timer
      add_second();
#ifdef TIMEMSGS      
      printf("SIG_OVERFLOW1: NO DCF77 - INTERNAL MODE\r");
#endif
      bit_counter = 0;
      dcf77_buffer = 0;
      current_time_is_valid_dcf_time = 0;
   }

   // This is just for a neat output of the date/time data:
   // printf incredibly blows up the code
   printf("%d %d-%d-%d %d:%d:%d        \r", current_time_is_valid_dcf_time, 
   day, month, year, hour, min, sec);

}

SIGNAL (DCF77_INT) {
   struct DCF77 *tmp_buffer;
   // if rising edge, pulse signal starts
   if (bit_is_set(INT0_CONTROL, ISC00)) {
#ifdef DEBUGEDGES
      printf("DCF77_INT rising edge \r");
#endif
      unsigned int pulse_width = TCNT1;
      // add elapsed timer counts to timercounts
      timercounts = (unsigned int)(timercounts + pulse_width - INIT_TIMER_COUNT);
      // PIN5 PORTD set -> LED on
      PORTD |= (1<<PD5);
      // reset timer
      RESET_TIMER0;
      // UPDATE: beginning of the new minute, so don't add a second, but use the
      //         received data to set date/time
      if (setbeginningofminute == 1) {
         tmp_buffer = (struct DCF77 *)(unsigned long long)&dcf77_buffer;
         setbeginningofminute = 0;
#ifdef DEBUG
      printf("SIG_OVERFLOW1 bc=%d %d %d %d %d %d %d \r", bit_counter,
             flags.parity_min, tmp_buffer->parity_min,
             flags.parity_hour, tmp_buffer->parity_hour,
             flags.parity_date, tmp_buffer->parity_date);
#endif
         if (bit_counter == 59 && 
            flags.parity_min == tmp_buffer->parity_min && 
            flags.parity_hour == tmp_buffer->parity_hour &&
            flags.parity_date == tmp_buffer->parity_date) {
            // convert all values from BCD (1,2,4,8,10,20,...) to decimal
            min = tmp_buffer->min - ((tmp_buffer->min/16)*6);
            hour = tmp_buffer->hour - ((tmp_buffer->hour/16)*6);
            day = tmp_buffer->day - ((tmp_buffer->day/16)*6);
            month = tmp_buffer->month - ((tmp_buffer->month/16)*6);
            year = 2000 + tmp_buffer->year - ((tmp_buffer->year/16)*6);
            // we received all 59 bits, so we have the start of the minute
            sec = 0;
            current_time_is_valid_dcf_time = 1;
#ifdef TIMEMSGS
            printf("DCF77_INT: TIME SET\r");
#endif
         } else {
            current_time_is_valid_dcf_time = 0;
         }
         // clear bit counter and dcf77 buffer, regardless if DCF77 time was
         //    received correctly or not. Either we have no signal, or we are
         //    at the beginning of the minute, so time sync will work at the
         //    beginning of the next minute
         bit_counter = 0;
         dcf77_buffer = 0;
         timercounts = 0;
         // since the pulse' edge was rising, next interrupt must be triggered
         //    at the falling edge of the DCF-77 pulse
         INT0_CONTROL &= ~(1 << ISC00);
      } else { // UPDATE: no beginning of the minute: just add one second
         // check if at least 9X% of one second have elapsed
         if (timercounts > PERCENT9X) {
#ifdef DEBUG
            printf("timercounts=%u pulse_width=%u\r", timercounts, pulse_width);
#endif
            add_second();
            timercounts = 0;
            // since the pulse' edge was rising, next interrupt must be triggered
            //    at the falling edge of the DCF-77 pulse
            INT0_CONTROL &= ~(1 << ISC00);
         }
      }
      
      // This is just for a neat output of the date/time data:
      // printf incredibly blows up the code
      printf("%d %d-%d-%d %d:%d:%d        \r", current_time_is_valid_dcf_time, 
      day, month, year, hour, min, sec);
      
   } else {
#ifdef DEBUGEDGES
      printf("DCF77_INT falling edge \r");
#endif
      // Falling edge of pulse detected
      // Now we check for the actual pulse width
      unsigned int pulse_width = TCNT1;
      // PIN5 PORTD clear -> LED off
      PORTD &= ~(1<<PD5);
      // Reset timer: DCF-77 signal seems to be working, so we must not add
      //    one second in the timer interrupt service routine (ISR)
      // Remember: this additional stuff is necessary, since we're reseting the
      // timer to avoid false second counts due the timer interrupt
      // UPDATE: don't reset TIMER0 when the 58th bit comes in, so that
      //         the TIMER0 overflows when the 59th bit would occur (it doesn't
      //         occur, since DCF77 doesn't broadcast a 59th bit, but we want
      //         an accurate 59th second count)
      if (bit_counter < 58) {
         RESET_TIMER0;
      }
#ifdef DEBUG
      printf("bit_counter=%d\r", bit_counter);
#endif
      if (bit_counter == 58) { // = 59 after the end of this SIGNAL
         flag59 = 1;
      }
      // add elapsed timer counts to timercounts
      timercounts = (unsigned int)(timercounts + pulse_width - INIT_TIMER_COUNT);
      // Checking of the parity
      // We check only minute, hour and the date for partiy, so reset the
      // temporaty parity flag when minute, hour or date bit-stream starts
      if (bit_counter ==  21 || bit_counter ==  29 || bit_counter ==  36) {
         flags.parity_flag = 0;
      }
      // If the last bit of minute, hour or data is received, save our caluculated
      // parity flag
      if (bit_counter ==  28) {flags.parity_min = flags.parity_flag;};
      if (bit_counter ==  35) {flags.parity_hour = flags.parity_flag;};
      if (bit_counter ==  58) {flags.parity_date = flags.parity_flag;};
      // check if we received a 0 or a 1 bit: 100ms = 0; 200ms = 1
      // When there is a rising edge, we restart the timer. When the falling edge
      //   is detected, we save the current timer count register TCNT1 in pulse_width
      // We just check if the pulse width is bigger than 150ms, so we have a 1
      // (F_OSC / 1024)/100*85 = 85% of the counts of the timer (= 1 second)
      // 65535 - (F_OSC / 1024)/100*85) = count of the timer when 150ms are elapsed
      if (pulse_width > (65535 - (F_OSC / 1024)/100*85)) {
         // set the bit "bit_counter" in "dcf77_buffer" to 1
         dcf77_buffer = dcf77_buffer | ((unsigned long long) 1 << bit_counter);
         // If we've just set the initial 1 in i.e. the "day", we toggle the 
         //    parity to 1 to match the "day" value to even parity
         // That means, "day" + parity-bit = even number
         // If we've added the second 1, we toggle the parity back to 0
         flags.parity_flag = flags.parity_flag ^ 1;
      }
      // we just detected the falling edge; next interrupt is at rising edge again
      INT0_CONTROL |= (1 << ISC00);
      // prepare the bit_counter for the next received bit by incrementing it
      bit_counter++;
   }
}

void DCF77_init (void) {
   //DCF77_INT_ENABLE();
   GICR |= (1 << INT0);
   // set INT0 to edge detection
   INT0_CONTROL |= (1 << ISC01);
   // timer overflow interrupt enable
   timer_enable_int(_BV(TOIE1));
   // timer prescaler to 1024 
   TCCR1B |= (1<<CS10 | 0<<CS11 | 1<<CS12); 
   // init timer
   RESET_TIMER0;
}
// end DCF77

int main(void) {
   init();
   sei();
   
   printf("testing printf  \r");

   DCF77_init();

   // enable  PD5 as output
   DDRD |= (1<<PD5);
   while (1) {
   }
   return 0;
}

